4. Executive Summary
Today,technologymade dating much easier since online dating services help to connect millions of single (or not) people together. Approximately 15% of U.S. adults report they have used online dating services or mobile dating apps (source http://www.pewresearch.org/fact-tank/2016/02/29/5-facts-about-online-dating/) So the question is how to identify the right people out of millions of online users to meet with. To this question, Ikkei and Antoine (two data scientists from Columbia University) brought insights on the data in order to help their two friends, Sarah and John.
While most of the users are men in their 20s, there are still some diversity in terms of age for the OKCupid users. In order to help our friends in their search, we aggregated data to narrow down their options and to save them some time.

Based on approximately 5 thousand profiles in the San Francisco area, where Sarah and John live, various types of people with different background, hobbies, characteristics and work industries can be found.

With approximately 71% of male visiting the website, the task seems easier for Sarah, but based on her strict criteria of age, education, job group or income, it might be a hustle.
Through this paper, we will perform detailed analysis to find the perfect match and understand the distribution and the correlation of the features. Lastly, our ultimate objective is help Sarah and John to find their potential match by advising the right people to meet with.

We sucssessfully identified the potential users who our friends, Sarah and John, might be interested in based on their personal preferences. Lastly, we can further inprove this analysis by * analyzing the self introductions (free textc fields), in order to understand more about the people * use machine learning to match our friends to the right people
5. Main Analysis
To start with, import all the necessary libraries.
library(ggplot2)
library(ggmap)
library(maps)
library(mapdata)
library(tidyr)
library(dplyr)
library(scales)
library(GGally)
library(stringr)
library(viridis)
library(parcoords)
library(stringi)
library(alluvial)
5.1 Import and clean data
The first step is to import the data and to perform some pre-processing/cleaning.
Here are out steps of the cleaning process:
Import the raw data, and create Variable called “profiles”.
Transform the data into known data types such as numeric values, dates or strings.
Select the complete profiles, meaning the users who completed all key features. We also decided to drop the column containing essays (Long strings describing mostly personality). The variable will later be called “complete_profile”.
Import the two pre-processed mapping files. This step will be useful in terms of binning categories and make graphs easier to read.
“Merge” allow us to work from one unique dataframe, having all information grouped together.
# import dataset
okcupid_raw <- read.csv('../profiles.csv',header = TRUE,sep = ',')
# seperate city and state into two different column
okcupid <- okcupid_raw %>% separate(location, c('City','State'), sep =',')
okcupid$height <- as.numeric(as.character(okcupid$height))
okcupid$income <- as.numeric(as.character(okcupid$income))
okcupid$age <- as.numeric(as.character(okcupid$age))
complete_profile <- okcupid %>% filter(age!='' & body_type!='' & diet!='' & drinks!='' & education!='' & ethnicity!='' &
height>40 & income > 0 & job!='' & City!='' & orientation!='' & religion != '' &
sex!='' & status!='')
drops <- c('essay0', 'essay1', 'essay2', 'essay3', 'essay4', 'essay5', 'essay6', 'essay7', 'essay8', 'essay9', 'last_online',
'smokes', 'drugs', 'offspring', 'pets', 'sign', 'speaks')
complete_profile <- complete_profile[ , !(names(complete_profile) %in% drops)]
job_map <- read.csv('job_map.csv',header = TRUE,sep = ',')
complete_profile <- merge(x=complete_profile, y=job_map, by='job', all.x = TRUE)
edu_map <- read.csv('education_map.csv',header = TRUE,sep = ',')
complete_profile <- merge(x=complete_profile, y=edu_map, by='education', all.x = TRUE)
geo <- read.csv('data_county.csv',header = TRUE,sep = ',')
5.2 Data exploration by gender
To start with the initial analyse, we decided to look at the distribution of the gender, males vs females. This visualization confirmed our intuition and found out that it might be easier for Sarah than for John to find a match.
ggplot(data=complete_profile, aes(sex, fill=sex)) +
geom_bar(col=1) +
ggtitle('Profiles by Gender') +
theme(plot.title = element_text(hjust = 0.5)) +
scale_x_discrete(labels=c('Female', 'Male'))

A second visualization is the distribution of age for the “complete_profile”. Our friends are in their late 20s and 30s and looking for partners who are in their age group. The distribution graph help us to understand what age group we are more likely to encounter. It seems to be a Gaussian distribution with a mean around 27 year old.
gender_labels = c(`f` = 'Female', `m` = 'Male')
ggplot(complete_profile, aes(age, fill=sex)) +
geom_histogram(col=1, binwidth=3) +
facet_grid(~sex, labeller = as_labeller(gender_labels)) +
ggtitle('Age Distribution by Gender') +
theme(plot.title = element_text(hjust = 0.5))

We were also interested in the relationship status of the users. They are mostly single, but there are more mysterious labels, ie “available” and “seeing someone”. Interesting point is that some married people are active OKCupid users. This is an important observation, since Sarah is really looking for someone single to start a sincere relationship.
ggplot(complete_profile, aes(status, fill=sex)) +
geom_bar(col=1) +
facet_grid(~sex, labeller = as_labeller(gender_labels)) +
ggtitle('Status Distribution by Gender') +
theme(plot.title = element_text(hjust = 0.5)) +
theme(axis.text.x = element_text(angle = 90, hjust = 1))

Since there are mostly single people using the service, we wanted to analyze more about the distribution of people who are not single. Around 100 people are married, which represents 1.6% of the users. For the rest, we see that people in “available” or “seeing someone” categories are evenly balanced, representing around 7.2% of this dataset.
ggplot(filter(complete_profile, !status %in% c('single', 'unknown')), aes(status, fill=sex)) +
geom_bar(col=1) +
facet_grid(~sex, labeller = as_labeller(gender_labels)) +
ggtitle('People Are Always Looking For Dates') +
theme(plot.title = element_text(hjust = 0.5)) +
theme(axis.text.x = element_text(angle = 90, hjust = 1))

As Sarah cares about meeting a wealthy guy, we focused on the income data. With a boxplot, we analyzed the distribution of income by gender. Since we have some outlines making a lot of money, it is difficult to understand the distribution of the income.
ggplot(complete_profile, aes(sex, income, fill=sex)) +
geom_boxplot() +
ggtitle('Income by Gender') +
theme(plot.title = element_text(hjust = 0.5)) +
scale_y_continuous(labels = comma) +
scale_x_discrete(labels=c('Female', 'Male'))

To understand the distribution better, we decided to zoom into the income ranging from $20K to $170K. We can see that men tend to make more (median is higher for men than for women, the 75% percentile as well).
ggplot(complete_profile, aes(sex, income, fill=sex)) +
geom_boxplot() +
ggtitle(bquote(atop(bold(.('Income by Gender')), atop(.('Just zoomed in version'), '')))) +
theme(plot.title = element_text(hjust = 0.5)) +
scale_y_continuous(limits = c(20000, 170000), labels = comma) +
scale_x_discrete(labels=c('Female', 'Male'))

5.3 Data exploration by body type
Another interesting study is to understand how users define themselves. As defining body type is a subjective, we wanted to visualize the distribution of body types based on relationship status. To do so, we sorted body types by BMI (we also sorted “rather not say” and “used up” at the top of the list, since those two criteria don’t indicate any BMI reference). From this distribution, we can see that most of the people consider themselves “average”, “athletic” or “fit”.
body_type_order = c('rather not say', 'used up', 'skinny', 'thin', 'average', 'athletic', 'fit', 'a little extra', 'curvy',
'full figured', 'jacked', 'overweight')
complete_profile$body_type = factor(complete_profile$body_type, levels=body_type_order)
body_type_data <- complete_profile %>% group_by(sex,body_type,status) %>% summarise(Count=n())
ggplot(body_type_data, aes(x = status, y = Count, fill = body_type)) +
geom_bar(stat='identity', position='dodge', col=1) +
xlab('Status') +
ylab('Frequence') +
ggtitle('Distribution of the Body Type By Relationship Status') +
theme(plot.title = element_text(hjust = 0.5)) +
scale_fill_brewer(palette='Paired')

To understand the difference on how women and men classify themselves, we decided to facet wrap the distribution of single users. Interestingly, the distribution for women is more evenly balanced between “average”, “fit” and “curvy”.
single <- filter(complete_profile,complete_profile$status=='single')
grouped_single <- single %>% group_by(status, sex, body_type) %>% summarise(Count=n())
ggplot(grouped_single, aes(x=status, y=Count)) +
geom_bar(stat='identity', position='dodge', col=1, aes(fill=body_type)) + xlab('Body_type') + ylab('Count') +
ggtitle('Count distribution of Body Types by Gender') + theme(plot.title = element_text(hjust = 0.5)) +
facet_grid(~sex, labeller = as_labeller(gender_labels)) +
scale_fill_brewer(palette='Paired')

In terms of density, again the around 25% of female and male consider themselves as average.
single <- filter(complete_profile,complete_profile$status=='single')
grouped_single <- single %>% group_by(status, sex, body_type) %>% summarise(Count_2=n()) %>% mutate(freq = Count_2 / sum(Count_2))
ggplot(grouped_single, aes(x=status, y=freq)) +
geom_bar(stat='identity', position='dodge', col=1, aes(fill=body_type)) + xlab('Body_type') + ylab('Count') +
ggtitle('Density distribution of Body Types by Gender') + theme(plot.title = element_text(hjust = 0.5)) +
facet_grid(~sex, labeller = as_labeller(gender_labels)) +
scale_fill_brewer(palette='Paired')

5.4 Income analysis by location
In order for Sarah and John to find the right people, we want to recommend some good areas to maximize the likelihood to find a partner. To answer this question, we decided to analyze the locations of the users that make more than $150K (this value can vary if needed). To do so, we first built a map of California and aggregated by income and county (our R variable is called “sub-region”).
usa <- map_data('usa')
w2hr <- map_data('world2Hires')
states <- map_data('state')
counties <- map_data('county')
complete_profile$State <- trimws(complete_profile$State)
city_count <- complete_profile[complete_profile$income >= 150000,] %>% group_by(City,sex) %>% summarize(Frequence = n())
geo$city <- tolower(geo$city)
geo$county <- tolower(geo$county)
geo <- filter(geo, geo$state=='CA')
geo <- geo[,c('city','county')]
geo <- unique(geo)
city_count <- merge(x=city_count, y=geo, by.x='City', by.y='city')
city_count <- city_count %>% group_by(county) %>% summarize(Count = sum(Frequence))
colnames(city_count)[1] <-'region'
california <- subset(states, region == 'california')
ca_counties <- subset(counties, region == 'california')
colnames(city_count)[1] <-'subregion'
ca_data <- inner_join(ca_counties, city_count, by = 'subregion')
no_axe <- theme(
axis.text = element_blank(),
axis.line = element_blank(),
axis.ticks = element_blank(),
panel.border = element_blank(),
panel.grid = element_blank(),
axis.title = element_blank()
)
ca_map <- ggplot(data = california, mapping = aes(x = long, y = lat, group = group)) +
coord_fixed(1.3) +
geom_polygon(color = 'black', fill = 'gray') +
scale_fill_viridis()
ca_map <- ca_map +
geom_polygon(data = ca_data, aes(fill = Count), color = 'white') +
geom_polygon(color = 'black', fill = NA) +
theme_bw() +
ggtitle('Count of people earning more than 150K a year') +
no_axe
ca_map

We then zoomed on San Francisco area. The richer users are located in the Silicon Valley and in San Francisco. The surrounding neighborhoods are less wealthy. Hence, we would recommend Sarah and John to meet the people in the Silicon Valley and in San Francisco.
ca_map + coord_fixed(xlim = c(-123, -120.0), ylim = c(35, 40), ratio = 1)

5.5 Relationship of income and other key variables with pairs plot
# decided to set the uppper limit of $250k to filter out the suspicious profiles
# and also our friends might not fit well with thoese people
complete_profile <- complete_profile %>% filter(income <= 250000 & age < 60)
data_ggpairs <- complete_profile[,c('age','income', 'height','education_level')]
data_ggpairs <- data_ggpairs[!rowSums(data_ggpairs[2] >200000),]
my_fn <- function(data, mapping, method='loess', ...){
p <- ggplot(data = data, mapping = mapping) +
geom_point() +
geom_smooth(method=method, ...)
p
}
ggpairs(data_ggpairs, lower = list(continuous = my_fn))

This pairs plot shows some interesting factors to find an ideal partner for John and Sarah.
Age
- Age graph (row 1, column 1): most of the OKCupid are in 20s and 30s, which is a great news for our friends.
- Age and income graph (row 2, column 1): income goes up till people turn around 40 years old and then gradually goes down.
- Age and height (row 3, column 1): no relationship between age and height. It is a good news for Sarah as she likes tall guys.
- Age and education (row 4, column 1): no surprise here. As people earn higher education they get older. 7 is PhD, 6 master, 5 bachelor etc..
Income
- Income graph (row 2, column 2): there are four small humps. The first hump is at $20,000. Mainly students are in this group. Second hump is at $80,000. This data set is for San Francisco area, where many engineers are. The average income for them is $84,000, which is inline with this group. We aslo see a hump around $100,000. We consider this is due to fact that people like to say $100,000 if their income is relatively close to it. The last one is at $1M, which is the upper limit for OKCupid. Some people who actually make more money would choose it. Another reason we can think of is that some people who tired to be funny or decided not to answer this question and chose it.
- Income and height (row 3, column 2): there is no relationship between income and height
- Income and education (row 4, column 2): there is a slight relationship. As people earn higher education their income tends to go up a bit
Height
- Height graph (row 3, column 3): height is normally distributed
Education
- Education graph (row 3, column 3): there are two humps. The first one is at 2, which is a student group. Data shows that a lot of students use OKCupid. Despite the fact that there are manyopportunities to socialize and meet new people. The other one is at 5, which is bachelor degree.
5.6 Relationship of income and other key variables with heatmap
complete_profile$age_bin <- cut(complete_profile$age, breaks=seq(10, 70, by=5))
income_count_age_job <- complete_profile %>% group_by(age_bin, job_group) %>% summarise(IncomeCount = n(), AverageIncome = mean(income))
title <- 'Average Income by Job and Age'
subheader <- 'White numbers indicate the count'
ggplot(income_count_age_job, aes(age_bin, job_group, fill = AverageIncome)) +
geom_tile() +
geom_text(aes(label = IncomeCount), colour = "white", size = 3) +
ggtitle(bquote(atop(bold(.(title)), atop(.(subheader), "")))) +
xlab('Age') +
ylab('Job group') +
theme(plot.title = element_text(hjust = 0.5)) +
guides(color = FALSE) +
scale_fill_viridis()

This heatmap tells some interesting stories.
- People in travel industries do not make much money regardless of their age
- From this data we can see that people in tech are relatively doing well. Given the fact that this for San Francisco, it makes sense. Sarah would be happy to know that there are 331 people who fit in her criteria.
- People in medical field make good money later in their career.
- Executive is the exception here. It defines the seniority in companies. All the other ones are industries. So there is no surprise that executives make more money than other groups.
- Artist is an interesting category. There are a lot of artists who are in their 20s and 30s and they do not make much money. However, once they get into their 40s, the population significantly decreases and the average income goes up close to $100,000.
income_count_sex_job <- complete_profile %>% group_by(sex, job_group) %>% summarise(IncomeCount = n(), AverageIncome = mean(income))
options(scipen=10000)
ggplot(income_count_sex_job, aes(sex, job_group, fill = AverageIncome)) +
geom_tile() +
geom_text(aes(label = sprintf('$%s', format(round(AverageIncome, 0), big.mark=','))), colour = 'red', size = 3) +
ggtitle('Average Income by Job and Gender') +
xlab('Gender') +
ylab('Job group') +
theme(plot.title = element_text(hjust = 0.5)) +
guides(color = FALSE) +
scale_fill_viridis() +
scale_x_discrete(labels=c('Female', 'Male'))

When we look at this heatmap, we can tell some more interesting stories.
- Travel industry is the only field where there is no income gap between genders.
- There are a lot less females in tech industry and also they make less money than males.
- Surprisingly people in medical field do not make much money. This is likely due to that fact that their salary is low during their residency and fellowship.
- Income gap among males and females is the largest for financial industry. Also male financial professionals make about the same as executives.
5.7 Find an ideal candidate!
#only select single people
education_order = c('other', 'in school', 'high school', 'associate', 'bachelor', 'master', 'phd')
complete_profile$education_group = factor(complete_profile$education_group, levels=education_order)
single <- complete_profile[complete_profile$status=='single',]
body_type_order = c('rather not say', 'used up', 'skinny', 'thin', 'average', 'athletic', 'fit', 'a little extra', 'curvy',
'full figured', 'jacked', 'overweight')
complete_profile$body_type = factor(complete_profile$body_type, levels=body_type_order)
single$age_bin <- cut(single$age, breaks=seq(10, 70, by=10))
single$age_bin <- stri_sub(single$age_bin, 2,3)
single$income_k <- single$income / 1000
single$income_k[single$income_k >= 200] <- 200
grouped <- rename(count(single, sex, age_bin, education_group, job_group, income_k), Freq = n)
alluvial(grouped[,1:5], freq=grouped$Freq, border=NA,
hide = grouped$Freq < quantile(grouped$Freq, .75),
col=ifelse(grouped$income_k >= 150, 'red', 'gray'))

This alluvial plot shows how the key factors relate to each other.
- First thing we notice is that most of the people who make more than $150,000 are male. Even taking the fact that there are more male OKCupid users into consideration, this was a surprise for us and John would not be too happy to hear that.
- Second, we see some tendency that people who have earned higher education make more money.
- Third, a lot of the wealthy users are male who are their 30s and 40s. This is a great news for Sarah
data_ggpar <- complete_profile[,c('sex', 'orientation', 'status', 'age', 'height','body_type', 'drinks', 'job_group', 'income')]
parcoords(data_ggpar
,rownames = F
,brushMode = '1D-axes'
,reorderable = T
,queue = T
,color = list(colorBy = 'Region' ,colorScale = htmlwidgets::JS('d3.scale.category10()')))
This parallel coordinate plot is very powerful. It lets our friends find exactly the people they would like to meet. Here are how their ideal candidates would look like. They have different preferences but it looks like both of them have many options.
- Ideal candidates for John 
- Ideal candidates for Sarah 
LS0tCnRpdGxlOiAnRmluYWwgUHJvamVjdCcKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQpBbnRvaW5lIE1hcnRoZXk6IGFtNDY2NSAKCklra2VpIEl0b2t1OiBpaTIxNTUKCiMgMS4gSW50cm9kdWN0aW9uCgpXZSBkZWNpZGVkIHRvIGFuYWx5emUgT0tDdXBpZCBkYXRhIHRvIGhlbHAgb3VyIGZyaWVuZHMuIFtPS0N1cGlkXShodHRwczovL3d3dy5va2N1cGlkLmNvbS8pIGlzIGEgcG9wdWxhciBvbmxpbmUgZGF0aW5nIHNlcnZpY2UgYW5kIGl0IGhhcyAxMCBtaWxsaW9uIG1lbWJlcnMgYW5kIDEgbWlsbGlvbiBhY3RpdmUgdXNlcnMuIE91ciBmcmllbmRzLCBKb2huIGFuZCBTYXJhaCBoYXZlIGJlZW4gZ29pbmcgdG8gZGF0ZXMgZm9yIGEgd2hpbGUgYnV0IHRoZXkgYXJlIGhhdmluZyBhIGhhcmQgdGltZSBmaW5kaW5nIHRoZSByaWdodCBwZW9wbGUgdG8gYmUgdGhlaXIgcG90ZW50aWFsIGJveWZyaWVuZC9naXJsZnJpZW5kLiBTbyB3ZSBkZWNpZGVkIHRvIGhlbHAgb3VyIGZyaWVuZHMgdXRpbGl6aW5nIG91ciBkYXRhIGFuYWx5c2lzIGFuZCB2aXN1YWxpemF0aW9uIHNraWxscy4gSGVyZSBhcmUgdGhlaXIgcHJvZmlsZXMuIAoKRnJpZW5kIE5hbWUgICB8QWdlIHxFdGhuaWNpdHkgfEhlaWdodCB8T2NjdXBhdGlvbiAgICAgICAgfE5vdGVzICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAotLS0tLS0tLS0tLS0tLXwtLS0tfC0tLS0tLS0tLS18LS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18CkpvaG4gICAgICAgICAgfDI3ICB8V2hpdGUgICAgIHw1LjkgICAgfFNvZnR3YXJlIEVuZ2luZWVyIHxIZSBncmFkdWF0ZWQgZnJvbSBDb2x1bWJpYSBFbmdpbmVlcmluZyBTY2hvb2wgMyB5ZWFycyBhZ28gbGl2aW5nIGluIFNhbiBGcmFuY2lzY28uIEhlIGlzIGFuIGVhc3kgZ29pbmcgYW5kIGZ1biBwZXJzb24gdG8gaGFuZyBvdXQgd2l0aC4gSGUgaXMgcXVpdGUgYXRobGV0aWMgc28gaGUgd291bGQgbGlrZSBhIHdvbWFuIHdobyBhbHNvIGxpa2VzIHRvIGdvIHRvIGd5bSBvciB3YXRjaCBzcG9ydCBnYW1lcyB0b2dldGhlci4gSGUgd2FudHMgaGVyIHRvIGJlIGFyb3VuZCBoaXMgYWdlLiBObyBwcmVmZXJlbmNlIGZvciBldGhuaWNpdHkgb3IgcmVsaWdpb24gfApTYXJhaCAgICAgICAgIHwzNSAgfEFzaWFuICAgICB8NS4zICAgIHxCb3V0aXF1ZSBCdXNpbmVzcyBPd25lcnxTaGUgaXMgYSB2ZXJ5IHN1Y2Nlc3NmdWwgZW50cmVwcmVuZXVyLiBTaGUgb3ducyA5IGJvdXRpcXVlIHN0b3JlcyBpbiBPYWtsYW5kIENBLiBTaGUgZHJvcHBlZCBvdXQgb2YgY29sbGVnZSB0byBvcGVuIHVwIGhlciBkcmVhbSBzdG9yZS4gU2luY2UgdGhlbiBzaGUgaGFzIGJlZW4gd29ya2luZyBxdWl0ZSBoYXJkIGFuZCBzaGUgaGFzIG5vIHRpbWUgdG8gZmluZCBhIGJveWZyaWVuZC4gTW9zdCBvZiBoZXIgY28td29ya2VycyBhcmUgd29tZW4gc28gc2hlIGRvZXMgbm90IGhhdmUgbXVjaCBvcHBvcnR1bml0aWVzIHRvIG5ldyBtZW4gYXQgd29yay4gU2hlIGlzIGxvb2tpbmcgZm9yIGEgbWFuIHdobyBpcyBhbHNvIHdlYWx0aHkuIFNoZSBsaWtlcyB0YWxsIGFuZCBmaXQgZ3V5cy4gU2hlIGlzIG5vdCBpbnRlcmVzdGVkIGluIHlvdW5nIGNvbGxlZ2Ugc3R1ZGVudHMuIE90aGVyIHRoYW4gdGhhdCBzaGUgaXMgb3Blbi4KCgpXZSBmb3VuZCB0aGF0IGEgZ3JvdXAgb2YgcmVzZWFyY2hlcnMgYXQgTWlkZGxlYnVyeSBDb2xsZWdlIGFuZCBSZWVkIENvbGxlZ2Ugc2NyYXBlZCBPS0N1cGlkIHByb2ZpbGUgcGFnZXMgYW5kIHdlIGRlY2lkZWQgdG8gdXRpbGl6ZSBpdC4gVGhlIGRhdGEgY2FuIGJlIGZvdW5kIGhlcmUgW2h0dHBzOi8vZ2l0aHViLmNvbS9ydWRlYm95YmVydC9KU0VfT2tDdXBpZC9ibG9iL21hc3Rlci9wcm9maWxlcy5jc3YuemlwXShodHRwczovL2dpdGh1Yi5jb20vcnVkZWJveWJlcnQvSlNFX09rQ3VwaWQvYmxvYi9tYXN0ZXIvcHJvZmlsZXMuY3N2LnppcCkgQWZ0ZXIgZXhwbG9yaW5nIHRoZSBkYXRhLCB3ZSBjcmVhdGVkIG91ciBvd24gbWFwcGluZywgd2hpY2ggaXMgaW5jbHVkZWQgYWxvbmcgd2l0aCB0aGlzIG5vdGVib29rLCB0byBjbGVhbiB1cCBhbmQgYWdncmVnYXRlIGxhYmVscyBmb3IgZWR1Y2F0aW9uIGFuZCBqb2JzLiBXZSB3aWxsIGV4cGxhaW4gdGhpcyBtYXBwaW5nIHN0ZXAgbW9yZSBpbiBkZXRhaWxzIGluICJBbmFseXNpcyBvZiBEYXRhIFF1YWxpdHkiIHNlY3Rpb24gYmVsb3cuIAoKCiMgMi4gVGVhbQpBcyBjdXJyZW50IHN0dWRlbnRzIGF0IHRoZSBEYXRhIFNjaWVuY2UgSW5zdGl0dXRlIG9mIENvbHVtYmlhIFVuaXZlcnNpdHksIHdlIGRlY2lkZWQgdG8gaGVscCBvdXIgZnJpZW5kcyBleHBsb3JpbmcgdGhlIE9LQ3VwaWQgZGF0YXNldCBhbmQgYXBwbHlpbmcgdmlzdWFsaXphdGlvbiB0ZWNobmlxdWUgaW4gb3JkZXIgdG8gZmluZCB0aGUgcmlnaHQgcGVvcGxlIHRvIHNoYXJlIHRoZWlyIGxpZmUuIFdlIHdvcmsgY29uam9pbnRseSB0byBjbGVhbiBhbmQgc2xpY2UgYW5kIGRpY2UgdGhlIGRhdGEuIEJvdGggb2YgdXMgY3JlYXRlZCB0aGUgbWFwcGluZyBmaWxlcyB0byBicmluZyBtb3JlIGNvbnNpc3RlbmN5IHRvIG91ciByZXNlYXJjaC4gCgpJa2tlaSwgd2hvIGlzIGEgY2xvc2UgZnJpZW5kIG9mIEpvaG4sIHNwZW50IGhpcyB0aW1lIG9uIGZpbmRpbmcgcGF0dGVybnMgaW4gdGhlIGRhdGEgYW5kIGJ1aWxkaW5nIHZhcmlldGllcyBvZiBkaXN0cmlidXRpb24gY2hhcnRzIHRvIHVuZGVyc3RhbmQgaG93IHRvIHV0aWxpemUgT0tDdXBpZC4gSGlzIGlkZWEgd2FzIHRvIGlkZW50aWZ5IGFueSB0cmVuZHMgaW4gb3JkZXIgdG8gc2VlIGlmIG91ciBmcmllbmRzIHdpbGwgYmUgYWJsZSB0byBmaW5kIHRoZWlyIGZ1dHVyZSBib3lmcmllbmQvZ2lybGZyaWVuZC4KCkFudG9pbmUsIHdobyBpcyBhIGdvb2QgZnJpZW5kIG9mIFNhcmFoLCBzcGVudCBtb3N0IG9mIGhpcyB0aW1lIHRyeWluZyB0byByZWNvZ25pemUgaW50ZXJkZXBlbmRlbmNpZXMgYmV0d2VlbiB0aGUgZmVhdHVyZXMgYW5kIGhvdyB0aGV5IGludGVyYWN0IHdpdGggZWFjaCBvdGhlci4gVXNpbmcgdGhvc2UgdHdvIHN0dWRpZXMgd2UgY2FtZSB1cCB3aXRoIG91ciByZWNvbW1lbmRhdGlvbnMgYW5kIG90aGVyIHVzZWZ1bCBhZHZpY2UgdG8gYWNoaWV2ZSBKb2huIGFuZCBTYXJhaCdzIG9iamVjdGl2ZXMuCgojIDMuIEFuYWx5c2lzIG9mIERhdGEgUXVhbGl0eQpUaGlzIGRhdGEgd2FzIGFscmVhZHkgY2xlYW5lZCB1cCBieSB0aGUgcmVzZWFyY2hlcnMgdGhhdCBvcmlnaW5hbGx5IHNjcmFwZWQgdGhlIGRhdGEsIGVzcGVjaWFsbHkgZm9yIG5vbi10ZXh0IGNvbHVtbnMuIFNvIGl0IHdhcyByZWxhdGl2ZWx5IGNsZWFuLiBIb3dldmVyLCB0aGVyZSB3ZXJlIHN0aWxsIGEgZmV3IHN0ZXBzIHdlIG5lZWRlZCB0byBwZXJmb3JtLgoKIyMjIyAxKS4gRGF0YSBGaWx0ZXJpbmcKVGhlIG1haW4gdGFzayBoZXJlIGZvciB1cyB3YXMgdG8gc2VsZWN0IHRoZSByZWNvcmRzIHdpdGhvdXQgbWlzc2luZyB2YWx1ZXMuIFdlIGRlY2lkZWQgdG8gZGlzY2FyZCB0aGUgZGF0YSBpZiBhbnkgb2YgdGhlIGtleSBmaWVsZHMgd2VyZSBub3QgZmlsbGVkIG91dC4gVGhpcyBpcyB0byBtaXRpZ2F0ZSB0aGUgcmlzay4gRm9yIGV4YW1wbGUsIEpvaG4gYW5kIFNhcmFoIHdvdWxkIG5vdCB3YW50IHRvIG1lZXQgdGhlIHBlb3BsZSB3aG8gZGlkIG5vdCBmaWxsIG91dCB0aGVpciBnZW5kZXIuIEEgcHJvZmlsZSB3aXRob3V0IGdlbmRlciBzZWVtcyBvZGQuIFNpbWlsYXJseSwgaWYgam9iIGZpZWxkIHdhcyBsZWZ0IGJsYW5rLCB0aGlzIHBlcnNvbiBtaWdodCBub3QgaGF2ZSBhIGpvYiBvciBtaWdodCBiZSB0cnlpbmcgdG8gaGlkZSBzb21ldGhpbmcuIEZvciB0aGlzIHN0dWR5LCBzaW1wbHkgZGlzY2FyZGluZyB0aGVzZSByZWNvcmRzIGlzIHRoZSBhcHByb3ByaWF0ZSBhY3Rpb24uIFdlIGFyZSBub3QgdHJ5aW5nIHRvIHNvbHZlIG1hY2hpbmUgbGVhcm5pbmcgcHJvYmxlbS4gUmF0aGVyLCBvdXIgZ29hbCBpcyB0byBuYXJyb3cgZG93biB0aGUgY2hvaWNlcyBmb3Igb3VyIGZyaWVuZHMgc28gdGhhdCB0aGV5IGNhbiBvbmx5IG1lZXQgdGhlIHBlb3BsZSB0aGV5IGFyZSBsb29raW5nIGZvci4gSGVyZSBpcyBhIGxpc3Qgb2YgZmlsdGVycyB3ZSBhcHBsaWVkIGZvci4KCkZpZWxkICAgICAgICAgICAgfE5vdGUKLS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCmFnZSAgICAgICAgICAgICAgfENhbm5vdCBiZSBibGFuawpib2R5X3R5cGUgICAgICAgIHxDYW5ub3QgYmUgYmxhbmsKZGlldCAgICAgICAgICAgICB8Q2Fubm90IGJlIGJsYW5rCmRyaW5rcyAgICAgICAgICAgfENhbm5vdCBiZSBibGFuawplZHVjYXRpb24gICAgICAgIHxDYW5ub3QgYmUgYmxhbmsKZXRobmljaXR5ICAgICAgICB8Q2Fubm90IGJlIGJsYW5rCmpvYiAgICAgICAgICAgICAgfENhbm5vdCBiZSBibGFuawpDaXR5ICAgICAgICAgICAgIHxDYW5ub3QgYmUgYmxhbmsKb3JpZW50YXRpb24gICAgICB8Q2Fubm90IGJlIGJsYW5rCnJlbGlnaW9uICAgICAgICAgfENhbm5vdCBiZSBibGFuawpzZXggICAgICAgICAgICAgIHxDYW5ub3QgYmUgYmxhbmsKc3RhdHVzICAgICAgICAgICB8Q2Fubm90IGJlIGJsYW5rCmhlaWdodCAgICAgICAgICAgfEhhcyB0byBiZSBncmVhdGVyIHRoYW4gNDAgaW5jaAppbmNvbWUgICAgICAgICAgIHxIYXMgdG8gYmUgZ3JlYXRlciB0aGFuIDAKCiMjIyMgMikuIEN1c3RvbSBkYXRhIG1hcHBpbmcKQW5vdGhlciBpbXBvcnRhbnQgdGFzayBmb3IgdXMgd2FzIHRvIGNyZWF0ZSBvdXIgb3duIGRhdGEgbWFwcGluZyBmb3IgZWR1Y2F0aW9uIGFuZCBqb2IuIFdlIHdlbnQgdGhyb3VnaCBlYWNoIHJlY29yZCBhbmQgY2FyZWZ1bGx5IGNyZWF0ZWQgYW5kIHR1bmVkIHRoZXNlIG1hcHBpbmcgZGF0YSBzZXRzLiBGb3IgZXhhbXBsZSwgImdyYWR1YXRlZCBmcm9tIG1hc3RlcnMgcHJvZ3JhbSIgYW5kICJtYXN0ZXJzIHByb2dyYW0iIG1lYW4gZXhhY3RseSB0aGUgc2FtZSB0aGluZy4gSW4gYWRkaXRpb24sIHdlIGRlY2lkZWQgdG8gY2F0ZWdvcml6ZSAiZHJvcHBlZCBvdXQgb2YgcGguZCBwcm9ncmFtIiBhbHNvIGludG8gdGhlIHNhbWUgZ3JvdXAgYXMgaXQgaXMgbGlrZWx5IHRoYXQgdGhleSBoYXZlIGNvbXBsZXRlZCBtYXN0ZXIncyBkZWdyZWUgcHJpb3IgdG8gUGhEIHByb2dyYW1zIG9yIGhhdmUgYmVlbiBhd2FyZGVkIG1hc3RlcidzIGRlZ3JlZSBieSBjb21wbGV0aW5nIHRoZSBmaXJzdCB0d28geWVhcnMgb2YgUGhEIHByb2dyYW0uCgoKIyA0LiBFeGVjdXRpdmUgU3VtbWFyeQoKVG9kYXksdGVjaG5vbG9neW1hZGUgZGF0aW5nIG11Y2ggZWFzaWVyIHNpbmNlIG9ubGluZSBkYXRpbmcgc2VydmljZXMgaGVscCB0byBjb25uZWN0IG1pbGxpb25zIG9mIHNpbmdsZSAob3Igbm90KSBwZW9wbGUgdG9nZXRoZXIuCkFwcHJveGltYXRlbHkgMTUlIG9mIFUuUy4gYWR1bHRzIHJlcG9ydCB0aGV5IGhhdmUgdXNlZCBvbmxpbmUgZGF0aW5nIHNlcnZpY2VzIG9yIG1vYmlsZSBkYXRpbmcgYXBwcyAoc291cmNlIFtodHRwOi8vd3d3LnBld3Jlc2VhcmNoLm9yZy9mYWN0LXRhbmsvMjAxNi8wMi8yOS81LWZhY3RzLWFib3V0LW9ubGluZS1kYXRpbmcvXShodHRwOi8vd3d3LnBld3Jlc2VhcmNoLm9yZy9mYWN0LXRhbmsvMjAxNi8wMi8yOS81LWZhY3RzLWFib3V0LW9ubGluZS1kYXRpbmcvKSkgU28gdGhlIHF1ZXN0aW9uIGlzIGhvdyB0byBpZGVudGlmeSB0aGUgcmlnaHQgcGVvcGxlIG91dCBvZiBtaWxsaW9ucyBvZiBvbmxpbmUgdXNlcnMgdG8gbWVldCB3aXRoLiBUbyB0aGlzIHF1ZXN0aW9uLCBJa2tlaSBhbmQgQW50b2luZSAodHdvIGRhdGEgc2NpZW50aXN0cyBmcm9tIENvbHVtYmlhIFVuaXZlcnNpdHkpIGJyb3VnaHQgaW5zaWdodHMgb24gdGhlIGRhdGEgaW4gb3JkZXIgdG8gaGVscCB0aGVpciB0d28gZnJpZW5kcywgU2FyYWggYW5kIEpvaG4uCgpXaGlsZSBtb3N0IG9mIHRoZSB1c2VycyBhcmUgbWVuIGluIHRoZWlyIDIwcywgdGhlcmUgYXJlIHN0aWxsIHNvbWUgZGl2ZXJzaXR5IGluIHRlcm1zIG9mIGFnZSBmb3IgdGhlIE9LQ3VwaWQgdXNlcnMuIEluIG9yZGVyIHRvIGhlbHAgb3VyIGZyaWVuZHMgaW4gdGhlaXIgc2VhcmNoLCB3ZSBhZ2dyZWdhdGVkIGRhdGEgdG8gbmFycm93IGRvd24gdGhlaXIgb3B0aW9ucyBhbmQgdG8gc2F2ZSB0aGVtIHNvbWUgdGltZS4KYGBge3IsZWNobz1GQUxTRX0KbGlicmFyeShrbml0cikKa25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoJ1Nob3RfMS5wbmcnKQpgYGAKCkJhc2VkIG9uIGFwcHJveGltYXRlbHkgNSB0aG91c2FuZCBwcm9maWxlcyBpbiB0aGUgU2FuIEZyYW5jaXNjbyBhcmVhLCB3aGVyZSBTYXJhaCBhbmQgSm9obiBsaXZlLCB2YXJpb3VzIHR5cGVzIG9mIHBlb3BsZSB3aXRoIGRpZmZlcmVudCBiYWNrZ3JvdW5kLCBob2JiaWVzLCBjaGFyYWN0ZXJpc3RpY3MgYW5kIHdvcmsgaW5kdXN0cmllcyBjYW4gYmUgZm91bmQuIAoKYGBge3IsZWNobz1GQUxTRX0KbGlicmFyeShrbml0cikKa25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoJ1Nob3RfMi5wbmcnKQpgYGAKCldpdGggYXBwcm94aW1hdGVseSA3MSUgb2YgbWFsZSB2aXNpdGluZyB0aGUgd2Vic2l0ZSwgdGhlIHRhc2sgc2VlbXMgZWFzaWVyIGZvciBTYXJhaCwgYnV0IGJhc2VkIG9uIGhlciBzdHJpY3QgY3JpdGVyaWEgb2YgYWdlLCBlZHVjYXRpb24sIGpvYiBncm91cCBvciBpbmNvbWUsIGl0IG1pZ2h0IGJlIGEgaHVzdGxlLgoKVGhyb3VnaCB0aGlzIHBhcGVyLCB3ZSB3aWxsIHBlcmZvcm0gZGV0YWlsZWQgYW5hbHlzaXMgdG8gZmluZCB0aGUgcGVyZmVjdCBtYXRjaCBhbmQgdW5kZXJzdGFuZCB0aGUgZGlzdHJpYnV0aW9uIGFuZCB0aGUgY29ycmVsYXRpb24gb2YgdGhlIGZlYXR1cmVzLiBMYXN0bHksIG91ciB1bHRpbWF0ZSBvYmplY3RpdmUgaXMgaGVscCBTYXJhaCBhbmQgSm9obiB0byBmaW5kIHRoZWlyIHBvdGVudGlhbCBtYXRjaCBieSBhZHZpc2luZyB0aGUgcmlnaHQgcGVvcGxlIHRvIG1lZXQgd2l0aC4KCgpgYGB7cixlY2hvPUZBTFNFfQpsaWJyYXJ5KGtuaXRyKQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygnU2hvdF8zLnBuZycpCmBgYAoKV2Ugc3Vjc3Nlc3NmdWxseSBpZGVudGlmaWVkIHRoZSBwb3RlbnRpYWwgdXNlcnMgd2hvIG91ciBmcmllbmRzLCBTYXJhaCBhbmQgSm9obiwgbWlnaHQgYmUgaW50ZXJlc3RlZCBpbiBiYXNlZCBvbiB0aGVpciBwZXJzb25hbCBwcmVmZXJlbmNlcy4gTGFzdGx5LCB3ZSBjYW4gZnVydGhlciBpbnByb3ZlIHRoaXMgYW5hbHlzaXMgYnkgCiAgKiBhbmFseXppbmcgdGhlIHNlbGYgaW50cm9kdWN0aW9ucyAoZnJlZSB0ZXh0YyBmaWVsZHMpLCBpbiBvcmRlciB0byB1bmRlcnN0YW5kIG1vcmUgYWJvdXQgdGhlIHBlb3BsZQogICogdXNlIG1hY2hpbmUgbGVhcm5pbmcgdG8gbWF0Y2ggb3VyIGZyaWVuZHMgdG8gdGhlIHJpZ2h0IHBlb3BsZQoKIyA1LiBNYWluIEFuYWx5c2lzCgpUbyBzdGFydCB3aXRoLCBpbXBvcnQgYWxsIHRoZSBuZWNlc3NhcnkgbGlicmFyaWVzLgoKYGBge3IgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShnZ21hcCkKbGlicmFyeShtYXBzKQpsaWJyYXJ5KG1hcGRhdGEpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoc2NhbGVzKQpsaWJyYXJ5KEdHYWxseSkKbGlicmFyeShzdHJpbmdyKQpsaWJyYXJ5KHZpcmlkaXMpCmxpYnJhcnkocGFyY29vcmRzKQpsaWJyYXJ5KHN0cmluZ2kpCmxpYnJhcnkoYWxsdXZpYWwpCmBgYAoKIyMjIDUuMSBJbXBvcnQgYW5kIGNsZWFuIGRhdGEKClRoZSBmaXJzdCBzdGVwIGlzIHRvIGltcG9ydCB0aGUgZGF0YSBhbmQgdG8gcGVyZm9ybSBzb21lIHByZS1wcm9jZXNzaW5nL2NsZWFuaW5nLgoKSGVyZSBhcmUgb3V0IHN0ZXBzIG9mIHRoZSBjbGVhbmluZyBwcm9jZXNzOgoKMS4gSW1wb3J0IHRoZSByYXcgZGF0YSwgYW5kIGNyZWF0ZSBWYXJpYWJsZSBjYWxsZWQgInByb2ZpbGVzIi4KCjIuIFRyYW5zZm9ybSB0aGUgZGF0YSBpbnRvIGtub3duIGRhdGEgdHlwZXMgc3VjaCBhcyBudW1lcmljIHZhbHVlcywgZGF0ZXMgb3Igc3RyaW5ncy4KCjMuIFNlbGVjdCB0aGUgY29tcGxldGUgcHJvZmlsZXMsIG1lYW5pbmcgdGhlIHVzZXJzIHdobyBjb21wbGV0ZWQgYWxsIGtleSBmZWF0dXJlcy4gV2UgYWxzbyBkZWNpZGVkIHRvIGRyb3AgdGhlIGNvbHVtbiBjb250YWluaW5nIGVzc2F5cyAoTG9uZyBzdHJpbmdzIGRlc2NyaWJpbmcgbW9zdGx5IHBlcnNvbmFsaXR5KS4gVGhlIHZhcmlhYmxlIHdpbGwgbGF0ZXIgYmUgY2FsbGVkICJjb21wbGV0ZV9wcm9maWxlIi4KICAKNC4gSW1wb3J0IHRoZSB0d28gcHJlLXByb2Nlc3NlZCBtYXBwaW5nIGZpbGVzLiBUaGlzIHN0ZXAgd2lsbCBiZSB1c2VmdWwgaW4gdGVybXMgb2YgYmlubmluZyBjYXRlZ29yaWVzIGFuZCBtYWtlIGdyYXBocyBlYXNpZXIgdG8gcmVhZC4KCjUuICJNZXJnZSIgYWxsb3cgdXMgdG8gd29yayBmcm9tIG9uZSB1bmlxdWUgZGF0YWZyYW1lLCBoYXZpbmcgYWxsIGluZm9ybWF0aW9uIGdyb3VwZWQgdG9nZXRoZXIuCgpgYGB7ciByZXN1bHRzPSdoaWRlJywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBpbXBvcnQgZGF0YXNldApva2N1cGlkX3JhdyA8LSByZWFkLmNzdignLi4vcHJvZmlsZXMuY3N2JyxoZWFkZXIgPSBUUlVFLHNlcCA9ICcsJykKCiMgc2VwZXJhdGUgY2l0eSBhbmQgc3RhdGUgaW50byB0d28gZGlmZmVyZW50IGNvbHVtbgpva2N1cGlkIDwtIG9rY3VwaWRfcmF3ICU+JSBzZXBhcmF0ZShsb2NhdGlvbiwgYygnQ2l0eScsJ1N0YXRlJyksIHNlcCA9JywnKQoKb2tjdXBpZCRoZWlnaHQgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIob2tjdXBpZCRoZWlnaHQpKQpva2N1cGlkJGluY29tZSA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3Rlcihva2N1cGlkJGluY29tZSkpCm9rY3VwaWQkYWdlIDwtIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKG9rY3VwaWQkYWdlKSkKCmNvbXBsZXRlX3Byb2ZpbGUgPC0gb2tjdXBpZCAlPiUgZmlsdGVyKGFnZSE9JycgJiBib2R5X3R5cGUhPScnICYgZGlldCE9JycgJiBkcmlua3MhPScnICYgZWR1Y2F0aW9uIT0nJyAmIGV0aG5pY2l0eSE9JycgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhlaWdodD40MCAmIGluY29tZSA+IDAgJiAgam9iIT0nJyAmIENpdHkhPScnICYgb3JpZW50YXRpb24hPScnICYgcmVsaWdpb24gIT0gJycgJiAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXghPScnICYgc3RhdHVzIT0nJykKCmRyb3BzIDwtIGMoJ2Vzc2F5MCcsICdlc3NheTEnLCAnZXNzYXkyJywgJ2Vzc2F5MycsICdlc3NheTQnLCAnZXNzYXk1JywgJ2Vzc2F5NicsICdlc3NheTcnLCAnZXNzYXk4JywgJ2Vzc2F5OScsICdsYXN0X29ubGluZScsCiAgICAgICAgICAgJ3Ntb2tlcycsICdkcnVncycsICdvZmZzcHJpbmcnLCAncGV0cycsICdzaWduJywgJ3NwZWFrcycpCmNvbXBsZXRlX3Byb2ZpbGUgPC0gY29tcGxldGVfcHJvZmlsZVsgLCAhKG5hbWVzKGNvbXBsZXRlX3Byb2ZpbGUpICVpbiUgZHJvcHMpXQoKam9iX21hcCA8LSByZWFkLmNzdignam9iX21hcC5jc3YnLGhlYWRlciA9IFRSVUUsc2VwID0gJywnKQpjb21wbGV0ZV9wcm9maWxlIDwtIG1lcmdlKHg9Y29tcGxldGVfcHJvZmlsZSwgeT1qb2JfbWFwLCBieT0nam9iJywgYWxsLnggPSBUUlVFKQoKZWR1X21hcCA8LSByZWFkLmNzdignZWR1Y2F0aW9uX21hcC5jc3YnLGhlYWRlciA9IFRSVUUsc2VwID0gJywnKQpjb21wbGV0ZV9wcm9maWxlIDwtIG1lcmdlKHg9Y29tcGxldGVfcHJvZmlsZSwgeT1lZHVfbWFwLCBieT0nZWR1Y2F0aW9uJywgYWxsLnggPSBUUlVFKQoKZ2VvIDwtIHJlYWQuY3N2KCdkYXRhX2NvdW50eS5jc3YnLGhlYWRlciA9IFRSVUUsc2VwID0gJywnKQpgYGAKCiMjIyA1LjIgRGF0YSBleHBsb3JhdGlvbiBieSBnZW5kZXIKClRvIHN0YXJ0IHdpdGggdGhlIGluaXRpYWwgYW5hbHlzZSwgd2UgZGVjaWRlZCB0byBsb29rIGF0IHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIGdlbmRlciwgbWFsZXMgdnMgZmVtYWxlcy4gVGhpcyB2aXN1YWxpemF0aW9uIGNvbmZpcm1lZCBvdXIgaW50dWl0aW9uIGFuZCBmb3VuZCBvdXQgdGhhdCBpdCBtaWdodCBiZSBlYXNpZXIgZm9yIFNhcmFoIHRoYW4gZm9yIEpvaG4gdG8gZmluZCBhIG1hdGNoLgoKYGBge3J9CmdncGxvdChkYXRhPWNvbXBsZXRlX3Byb2ZpbGUsIGFlcyhzZXgsIGZpbGw9c2V4KSkgKyAKICBnZW9tX2Jhcihjb2w9MSkgKwogIGdndGl0bGUoJ1Byb2ZpbGVzIGJ5IEdlbmRlcicpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkgKwogIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzPWMoJ0ZlbWFsZScsICdNYWxlJykpCmBgYAoKQSBzZWNvbmQgdmlzdWFsaXphdGlvbiBpcyB0aGUgZGlzdHJpYnV0aW9uIG9mIGFnZSBmb3IgdGhlICJjb21wbGV0ZV9wcm9maWxlIi4gT3VyIGZyaWVuZHMgYXJlIGluIHRoZWlyIGxhdGUgMjBzIGFuZCAzMHMgYW5kIGxvb2tpbmcgZm9yIHBhcnRuZXJzIHdobyBhcmUgaW4gdGhlaXIgYWdlIGdyb3VwLiBUaGUgZGlzdHJpYnV0aW9uIGdyYXBoIGhlbHAgdXMgdG8gdW5kZXJzdGFuZCB3aGF0IGFnZSBncm91cCB3ZSBhcmUgbW9yZSBsaWtlbHkgdG8gZW5jb3VudGVyLiBJdCBzZWVtcyB0byBiZSBhIEdhdXNzaWFuIGRpc3RyaWJ1dGlvbiB3aXRoIGEgbWVhbiBhcm91bmQgMjcgeWVhciBvbGQuIAoKYGBge3J9CmdlbmRlcl9sYWJlbHMgPSBjKGBmYCA9ICdGZW1hbGUnLCBgbWAgPSAnTWFsZScpCgpnZ3Bsb3QoY29tcGxldGVfcHJvZmlsZSwgYWVzKGFnZSwgZmlsbD1zZXgpKSArIAogIGdlb21faGlzdG9ncmFtKGNvbD0xLCBiaW53aWR0aD0zKSArCiAgZmFjZXRfZ3JpZCh+c2V4LCBsYWJlbGxlciA9IGFzX2xhYmVsbGVyKGdlbmRlcl9sYWJlbHMpKSArCiAgZ2d0aXRsZSgnQWdlIERpc3RyaWJ1dGlvbiBieSBHZW5kZXInKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpCmBgYAoKV2Ugd2VyZSBhbHNvIGludGVyZXN0ZWQgaW4gdGhlIHJlbGF0aW9uc2hpcCBzdGF0dXMgb2YgdGhlIHVzZXJzLiBUaGV5IGFyZSBtb3N0bHkgc2luZ2xlLCBidXQgdGhlcmUgYXJlIG1vcmUgbXlzdGVyaW91cyBsYWJlbHMsIGllICJhdmFpbGFibGUiIGFuZCAic2VlaW5nIHNvbWVvbmUiLiBJbnRlcmVzdGluZyBwb2ludCBpcyB0aGF0IHNvbWUgbWFycmllZCBwZW9wbGUgYXJlIGFjdGl2ZSBPS0N1cGlkIHVzZXJzLiBUaGlzIGlzIGFuIGltcG9ydGFudCBvYnNlcnZhdGlvbiwgc2luY2UgU2FyYWggaXMgcmVhbGx5IGxvb2tpbmcgZm9yIHNvbWVvbmUgc2luZ2xlIHRvIHN0YXJ0IGEgc2luY2VyZSByZWxhdGlvbnNoaXAuCgpgYGB7cn0KZ2dwbG90KGNvbXBsZXRlX3Byb2ZpbGUsIGFlcyhzdGF0dXMsIGZpbGw9c2V4KSkgKyAKICBnZW9tX2Jhcihjb2w9MSkgKwogIGZhY2V0X2dyaWQofnNleCwgbGFiZWxsZXIgPSBhc19sYWJlbGxlcihnZW5kZXJfbGFiZWxzKSkgKwogIGdndGl0bGUoJ1N0YXR1cyBEaXN0cmlidXRpb24gYnkgR2VuZGVyJykgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxKSkKYGBgCgpTaW5jZSB0aGVyZSBhcmUgbW9zdGx5IHNpbmdsZSBwZW9wbGUgdXNpbmcgdGhlIHNlcnZpY2UsIHdlIHdhbnRlZCB0byBhbmFseXplIG1vcmUgYWJvdXQgdGhlIGRpc3RyaWJ1dGlvbiBvZiBwZW9wbGUgd2hvIGFyZSBub3Qgc2luZ2xlLiBBcm91bmQgMTAwIHBlb3BsZSBhcmUgbWFycmllZCwgd2hpY2ggcmVwcmVzZW50cyAxLjYlIG9mIHRoZSB1c2Vycy4gRm9yIHRoZSByZXN0LCB3ZSBzZWUgdGhhdCBwZW9wbGUgaW4gImF2YWlsYWJsZSIgb3IgInNlZWluZyBzb21lb25lIiBjYXRlZ29yaWVzIGFyZSBldmVubHkgYmFsYW5jZWQsIHJlcHJlc2VudGluZyBhcm91bmQgNy4yJSBvZiB0aGlzIGRhdGFzZXQuCgpgYGB7cn0KZ2dwbG90KGZpbHRlcihjb21wbGV0ZV9wcm9maWxlLCAhc3RhdHVzICVpbiUgYygnc2luZ2xlJywgJ3Vua25vd24nKSksIGFlcyhzdGF0dXMsIGZpbGw9c2V4KSkgKyAKICBnZW9tX2Jhcihjb2w9MSkgKwogIGZhY2V0X2dyaWQofnNleCwgbGFiZWxsZXIgPSBhc19sYWJlbGxlcihnZW5kZXJfbGFiZWxzKSkgKwogIGdndGl0bGUoJ1Blb3BsZSBBcmUgQWx3YXlzIExvb2tpbmcgRm9yIERhdGVzJykgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxKSkKYGBgCgpBcyBTYXJhaCBjYXJlcyBhYm91dCBtZWV0aW5nIGEgd2VhbHRoeSBndXksIHdlIGZvY3VzZWQgb24gdGhlIGluY29tZSBkYXRhLiBXaXRoIGEgYm94cGxvdCwgd2UgYW5hbHl6ZWQgdGhlIGRpc3RyaWJ1dGlvbiBvZiBpbmNvbWUgYnkgZ2VuZGVyLiBTaW5jZSB3ZSBoYXZlIHNvbWUgb3V0bGluZXMgbWFraW5nIGEgbG90IG9mIG1vbmV5LCBpdCBpcyBkaWZmaWN1bHQgdG8gdW5kZXJzdGFuZCB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBpbmNvbWUuCgpgYGB7cn0KZ2dwbG90KGNvbXBsZXRlX3Byb2ZpbGUsIGFlcyhzZXgsIGluY29tZSwgZmlsbD1zZXgpKSArIAogIGdlb21fYm94cGxvdCgpICsgCiAgZ2d0aXRsZSgnSW5jb21lIGJ5IEdlbmRlcicpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzPWMoJ0ZlbWFsZScsICdNYWxlJykpCmBgYAoKVG8gdW5kZXJzdGFuZCB0aGUgZGlzdHJpYnV0aW9uIGJldHRlciwgd2UgZGVjaWRlZCB0byB6b29tIGludG8gdGhlIGluY29tZSByYW5naW5nIGZyb20gJDIwSyB0byAkMTcwSy4gV2UgY2FuIHNlZSB0aGF0IG1lbiB0ZW5kIHRvIG1ha2UgbW9yZSAobWVkaWFuIGlzIGhpZ2hlciBmb3IgbWVuIHRoYW4gZm9yIHdvbWVuLCB0aGUgNzUlIHBlcmNlbnRpbGUgYXMgd2VsbCkuCgpgYGB7cn0KZ2dwbG90KGNvbXBsZXRlX3Byb2ZpbGUsIGFlcyhzZXgsIGluY29tZSwgZmlsbD1zZXgpKSArIAogIGdlb21fYm94cGxvdCgpICsgCiAgZ2d0aXRsZShicXVvdGUoYXRvcChib2xkKC4oJ0luY29tZSBieSBHZW5kZXInKSksIGF0b3AoLignSnVzdCB6b29tZWQgaW4gdmVyc2lvbicpLCAnJykpKSkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMjAwMDAsIDE3MDAwMCksIGxhYmVscyA9IGNvbW1hKSArCiAgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHM9YygnRmVtYWxlJywgJ01hbGUnKSkKYGBgCgojIyMgNS4zIERhdGEgZXhwbG9yYXRpb24gYnkgYm9keSB0eXBlCgpBbm90aGVyIGludGVyZXN0aW5nIHN0dWR5IGlzIHRvIHVuZGVyc3RhbmQgaG93IHVzZXJzIGRlZmluZSB0aGVtc2VsdmVzLiBBcyBkZWZpbmluZyBib2R5IHR5cGUgaXMgYSBzdWJqZWN0aXZlLCB3ZSB3YW50ZWQgdG8gdmlzdWFsaXplIHRoZSBkaXN0cmlidXRpb24gb2YgYm9keSB0eXBlcyBiYXNlZCBvbiByZWxhdGlvbnNoaXAgc3RhdHVzLiBUbyBkbyBzbywgd2Ugc29ydGVkIGJvZHkgdHlwZXMgYnkgQk1JICh3ZSBhbHNvIHNvcnRlZCAicmF0aGVyIG5vdCBzYXkiIGFuZCAidXNlZCB1cCIgYXQgdGhlIHRvcCBvZiB0aGUgbGlzdCwgc2luY2UgdGhvc2UgdHdvIGNyaXRlcmlhIGRvbid0IGluZGljYXRlIGFueSBCTUkgcmVmZXJlbmNlKS4gRnJvbSB0aGlzIGRpc3RyaWJ1dGlvbiwgd2UgY2FuIHNlZSB0aGF0IG1vc3Qgb2YgdGhlIHBlb3BsZSBjb25zaWRlciB0aGVtc2VsdmVzICJhdmVyYWdlIiwgImF0aGxldGljIiBvciAiZml0Ii4gCgpgYGB7cn0KYm9keV90eXBlX29yZGVyID0gYygncmF0aGVyIG5vdCBzYXknLCAndXNlZCB1cCcsICdza2lubnknLCAndGhpbicsICdhdmVyYWdlJywgJ2F0aGxldGljJywgJ2ZpdCcsICdhIGxpdHRsZSBleHRyYScsICdjdXJ2eScsIAogICAgICAgICAgICAgICAgICAgICdmdWxsIGZpZ3VyZWQnLCAnamFja2VkJywgJ292ZXJ3ZWlnaHQnKQpjb21wbGV0ZV9wcm9maWxlJGJvZHlfdHlwZSA9IGZhY3Rvcihjb21wbGV0ZV9wcm9maWxlJGJvZHlfdHlwZSwgbGV2ZWxzPWJvZHlfdHlwZV9vcmRlcikKCmJvZHlfdHlwZV9kYXRhIDwtIGNvbXBsZXRlX3Byb2ZpbGUgJT4lIGdyb3VwX2J5KHNleCxib2R5X3R5cGUsc3RhdHVzKSAlPiUgc3VtbWFyaXNlKENvdW50PW4oKSkKCmdncGxvdChib2R5X3R5cGVfZGF0YSwgYWVzKHggPSBzdGF0dXMsIHkgPSBDb3VudCwgZmlsbCA9IGJvZHlfdHlwZSkpICsKICBnZW9tX2JhcihzdGF0PSdpZGVudGl0eScsIHBvc2l0aW9uPSdkb2RnZScsIGNvbD0xKSArIAogIHhsYWIoJ1N0YXR1cycpICsgCiAgeWxhYignRnJlcXVlbmNlJykgKyAKICBnZ3RpdGxlKCdEaXN0cmlidXRpb24gb2YgdGhlIEJvZHkgVHlwZSBCeSBSZWxhdGlvbnNoaXAgU3RhdHVzJykgKyAKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGU9J1BhaXJlZCcpCmBgYAoKVG8gdW5kZXJzdGFuZCB0aGUgZGlmZmVyZW5jZSBvbiBob3cgd29tZW4gYW5kIG1lbiBjbGFzc2lmeSB0aGVtc2VsdmVzLCAgd2UgZGVjaWRlZCB0byBmYWNldCB3cmFwIHRoZSBkaXN0cmlidXRpb24gb2Ygc2luZ2xlIHVzZXJzLiBJbnRlcmVzdGluZ2x5LCB0aGUgZGlzdHJpYnV0aW9uIGZvciB3b21lbiBpcyBtb3JlIGV2ZW5seSBiYWxhbmNlZCBiZXR3ZWVuICJhdmVyYWdlIiwgImZpdCIgYW5kICJjdXJ2eSIuCgpgYGB7cn0Kc2luZ2xlIDwtIGZpbHRlcihjb21wbGV0ZV9wcm9maWxlLGNvbXBsZXRlX3Byb2ZpbGUkc3RhdHVzPT0nc2luZ2xlJykKZ3JvdXBlZF9zaW5nbGUgPC0gc2luZ2xlICU+JSBncm91cF9ieShzdGF0dXMsIHNleCwgYm9keV90eXBlKSAlPiUgc3VtbWFyaXNlKENvdW50PW4oKSkKCmdncGxvdChncm91cGVkX3NpbmdsZSwgYWVzKHg9c3RhdHVzLCB5PUNvdW50KSkgKwogIGdlb21fYmFyKHN0YXQ9J2lkZW50aXR5JywgcG9zaXRpb249J2RvZGdlJywgY29sPTEsIGFlcyhmaWxsPWJvZHlfdHlwZSkpICsgeGxhYignQm9keV90eXBlJykgKyB5bGFiKCdDb3VudCcpICsKICBnZ3RpdGxlKCdDb3VudCBkaXN0cmlidXRpb24gb2YgQm9keSBUeXBlcyBieSBHZW5kZXInKSArIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSArCiAgZmFjZXRfZ3JpZCh+c2V4LCBsYWJlbGxlciA9IGFzX2xhYmVsbGVyKGdlbmRlcl9sYWJlbHMpKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZT0nUGFpcmVkJykKYGBgCgpJbiB0ZXJtcyBvZiBkZW5zaXR5LCBhZ2FpbiB0aGUgYXJvdW5kIDI1JSBvZiBmZW1hbGUgYW5kIG1hbGUgY29uc2lkZXIgdGhlbXNlbHZlcyBhcyBhdmVyYWdlLgoKYGBge3J9CnNpbmdsZSA8LSBmaWx0ZXIoY29tcGxldGVfcHJvZmlsZSxjb21wbGV0ZV9wcm9maWxlJHN0YXR1cz09J3NpbmdsZScpCmdyb3VwZWRfc2luZ2xlIDwtIHNpbmdsZSAlPiUgZ3JvdXBfYnkoc3RhdHVzLCBzZXgsIGJvZHlfdHlwZSkgJT4lIHN1bW1hcmlzZShDb3VudF8yPW4oKSkgJT4lIG11dGF0ZShmcmVxID0gQ291bnRfMiAvIHN1bShDb3VudF8yKSkKZ2dwbG90KGdyb3VwZWRfc2luZ2xlLCBhZXMoeD1zdGF0dXMsIHk9ZnJlcSkpICsKICBnZW9tX2JhcihzdGF0PSdpZGVudGl0eScsIHBvc2l0aW9uPSdkb2RnZScsIGNvbD0xLCBhZXMoZmlsbD1ib2R5X3R5cGUpKSArIHhsYWIoJ0JvZHlfdHlwZScpICsgeWxhYignQ291bnQnKSArCiAgZ2d0aXRsZSgnRGVuc2l0eSBkaXN0cmlidXRpb24gb2YgQm9keSBUeXBlcyBieSBHZW5kZXInKSArIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSArCiAgZmFjZXRfZ3JpZCh+c2V4LCBsYWJlbGxlciA9IGFzX2xhYmVsbGVyKGdlbmRlcl9sYWJlbHMpKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZT0nUGFpcmVkJykKYGBgCgojIyMgNS40IEluY29tZSBhbmFseXNpcyBieSBsb2NhdGlvbgoKSW4gb3JkZXIgZm9yIFNhcmFoIGFuZCBKb2huIHRvIGZpbmQgdGhlIHJpZ2h0IHBlb3BsZSwgd2Ugd2FudCB0byByZWNvbW1lbmQgc29tZSBnb29kIGFyZWFzIHRvIG1heGltaXplIHRoZSBsaWtlbGlob29kIHRvIGZpbmQgYSBwYXJ0bmVyLiBUbyBhbnN3ZXIgdGhpcyBxdWVzdGlvbiwgd2UgZGVjaWRlZCB0byBhbmFseXplIHRoZSBsb2NhdGlvbnMgb2YgdGhlIHVzZXJzIHRoYXQgbWFrZSBtb3JlIHRoYW4gJDE1MEsgKHRoaXMgdmFsdWUgY2FuIHZhcnkgaWYgbmVlZGVkKS4gVG8gZG8gc28sIHdlIGZpcnN0IGJ1aWx0IGEgbWFwIG9mIENhbGlmb3JuaWEgYW5kIGFnZ3JlZ2F0ZWQgYnkgaW5jb21lIGFuZCBjb3VudHkgKG91ciBSIHZhcmlhYmxlIGlzIGNhbGxlZCAic3ViLXJlZ2lvbiIpLiAKCmBgYHtyfQp1c2EgPC0gbWFwX2RhdGEoJ3VzYScpCncyaHIgPC0gbWFwX2RhdGEoJ3dvcmxkMkhpcmVzJykKc3RhdGVzIDwtIG1hcF9kYXRhKCdzdGF0ZScpCmNvdW50aWVzIDwtIG1hcF9kYXRhKCdjb3VudHknKQoKY29tcGxldGVfcHJvZmlsZSRTdGF0ZSA8LSB0cmltd3MoY29tcGxldGVfcHJvZmlsZSRTdGF0ZSkKY2l0eV9jb3VudCA8LSBjb21wbGV0ZV9wcm9maWxlW2NvbXBsZXRlX3Byb2ZpbGUkaW5jb21lID49IDE1MDAwMCxdICAlPiUgZ3JvdXBfYnkoQ2l0eSxzZXgpICU+JSBzdW1tYXJpemUoRnJlcXVlbmNlID0gbigpKQoKZ2VvJGNpdHkgPC0gdG9sb3dlcihnZW8kY2l0eSkKZ2VvJGNvdW50eSA8LSB0b2xvd2VyKGdlbyRjb3VudHkpCgpnZW8gPC0gZmlsdGVyKGdlbywgZ2VvJHN0YXRlPT0nQ0EnKQpnZW8gPC0gZ2VvWyxjKCdjaXR5JywnY291bnR5JyldCmdlbyA8LSB1bmlxdWUoZ2VvKQoKY2l0eV9jb3VudCA8LSBtZXJnZSh4PWNpdHlfY291bnQsIHk9Z2VvLCBieS54PSdDaXR5JywgYnkueT0nY2l0eScpCmNpdHlfY291bnQgPC0gY2l0eV9jb3VudCAlPiUgZ3JvdXBfYnkoY291bnR5KSAlPiUgc3VtbWFyaXplKENvdW50ID0gc3VtKEZyZXF1ZW5jZSkpCgpjb2xuYW1lcyhjaXR5X2NvdW50KVsxXSA8LSdyZWdpb24nCgpjYWxpZm9ybmlhIDwtIHN1YnNldChzdGF0ZXMsIHJlZ2lvbiA9PSAnY2FsaWZvcm5pYScpCgpjYV9jb3VudGllcyA8LSBzdWJzZXQoY291bnRpZXMsIHJlZ2lvbiA9PSAnY2FsaWZvcm5pYScpCmNvbG5hbWVzKGNpdHlfY291bnQpWzFdIDwtJ3N1YnJlZ2lvbicKY2FfZGF0YSA8LSBpbm5lcl9qb2luKGNhX2NvdW50aWVzLCBjaXR5X2NvdW50LCBieSA9ICdzdWJyZWdpb24nKQoKbm9fYXhlIDwtIHRoZW1lKAogIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSwKICBheGlzLmxpbmUgPSBlbGVtZW50X2JsYW5rKCksCiAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwKICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwKICBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpCikKCmNhX21hcCA8LSBnZ3Bsb3QoZGF0YSA9IGNhbGlmb3JuaWEsIG1hcHBpbmcgPSBhZXMoeCA9IGxvbmcsIHkgPSBsYXQsIGdyb3VwID0gZ3JvdXApKSArCiAgY29vcmRfZml4ZWQoMS4zKSArCiAgZ2VvbV9wb2x5Z29uKGNvbG9yID0gJ2JsYWNrJywgZmlsbCA9ICdncmF5JykgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcygpCgpjYV9tYXAgPC0gY2FfbWFwICsKICBnZW9tX3BvbHlnb24oZGF0YSA9IGNhX2RhdGEsIGFlcyhmaWxsID0gQ291bnQpLCBjb2xvciA9ICd3aGl0ZScpICsKICBnZW9tX3BvbHlnb24oY29sb3IgPSAnYmxhY2snLCBmaWxsID0gTkEpICsKICB0aGVtZV9idygpICsKICBnZ3RpdGxlKCdDb3VudCBvZiBwZW9wbGUgZWFybmluZyBtb3JlIHRoYW4gMTUwSyBhIHllYXInKSArCiAgbm9fYXhlCmNhX21hcApgYGAKCldlIHRoZW4gem9vbWVkIG9uIFNhbiBGcmFuY2lzY28gYXJlYS4gVGhlIHJpY2hlciB1c2VycyBhcmUgbG9jYXRlZCBpbiB0aGUgU2lsaWNvbiBWYWxsZXkgYW5kIGluIFNhbiBGcmFuY2lzY28uIFRoZSBzdXJyb3VuZGluZyBuZWlnaGJvcmhvb2RzIGFyZSBsZXNzIHdlYWx0aHkuIEhlbmNlLCB3ZSB3b3VsZCByZWNvbW1lbmQgU2FyYWggYW5kIEpvaG4gdG8gbWVldCB0aGUgcGVvcGxlIGluIHRoZSBTaWxpY29uIFZhbGxleSBhbmQgaW4gU2FuIEZyYW5jaXNjby4KCmBgYHtyfQpjYV9tYXAgKyBjb29yZF9maXhlZCh4bGltID0gYygtMTIzLCAtMTIwLjApLCAgeWxpbSA9IGMoMzUsIDQwKSwgcmF0aW8gPSAxKQpgYGAKCiMjIyA1LjUgUmVsYXRpb25zaGlwIG9mIGluY29tZSBhbmQgb3RoZXIga2V5IHZhcmlhYmxlcyB3aXRoIHBhaXJzIHBsb3QKCmBgYHtyIHJlc3VsdHM9J2hpZGUnLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQoKIyBkZWNpZGVkIHRvIHNldCB0aGUgdXBwcGVyIGxpbWl0IG9mICQyNTBrIHRvIGZpbHRlciBvdXQgdGhlIHN1c3BpY2lvdXMgcHJvZmlsZXMgCiMgYW5kIGFsc28gb3VyIGZyaWVuZHMgbWlnaHQgbm90IGZpdCB3ZWxsIHdpdGggdGhvZXNlIHBlb3BsZQpjb21wbGV0ZV9wcm9maWxlIDwtIGNvbXBsZXRlX3Byb2ZpbGUgJT4lIGZpbHRlcihpbmNvbWUgPD0gMjUwMDAwICYgYWdlIDwgNjApCgpkYXRhX2dncGFpcnMgPC0gY29tcGxldGVfcHJvZmlsZVssYygnYWdlJywnaW5jb21lJywgJ2hlaWdodCcsJ2VkdWNhdGlvbl9sZXZlbCcpXQpkYXRhX2dncGFpcnMgPC0gZGF0YV9nZ3BhaXJzWyFyb3dTdW1zKGRhdGFfZ2dwYWlyc1syXSA+MjAwMDAwKSxdCgpteV9mbiA8LSBmdW5jdGlvbihkYXRhLCBtYXBwaW5nLCBtZXRob2Q9J2xvZXNzJywgLi4uKXsKICAgICAgcCA8LSBnZ3Bsb3QoZGF0YSA9IGRhdGEsIG1hcHBpbmcgPSBtYXBwaW5nKSArIAogICAgICBnZW9tX3BvaW50KCkgKyAKICAgICAgZ2VvbV9zbW9vdGgobWV0aG9kPW1ldGhvZCwgLi4uKQogICAgICBwCiAgICB9CgogICAKZ2dwYWlycyhkYXRhX2dncGFpcnMsIGxvd2VyID0gbGlzdChjb250aW51b3VzID0gbXlfZm4pKQpgYGAKClRoaXMgcGFpcnMgcGxvdCBzaG93cyBzb21lIGludGVyZXN0aW5nIGZhY3RvcnMgdG8gZmluZCBhbiBpZGVhbCBwYXJ0bmVyIGZvciBKb2huIGFuZCBTYXJhaC4KCiMjIyMjIEFnZQoqICoqQWdlIGdyYXBoKiogKHJvdyAxLCBjb2x1bW4gMSk6IG1vc3Qgb2YgdGhlIE9LQ3VwaWQgYXJlIGluIDIwcyBhbmQgMzBzLCB3aGljaCBpcyBhIGdyZWF0IG5ld3MgZm9yIG91ciBmcmllbmRzLiAKKiAqKkFnZSBhbmQgaW5jb21lKiogZ3JhcGggKHJvdyAyLCBjb2x1bW4gMSk6IGluY29tZSBnb2VzIHVwIHRpbGwgcGVvcGxlIHR1cm4gYXJvdW5kIDQwIHllYXJzIG9sZCBhbmQgdGhlbiBncmFkdWFsbHkgZ29lcyBkb3duLgoqICoqQWdlIGFuZCBoZWlnaHQqKiAocm93IDMsIGNvbHVtbiAxKTogbm8gcmVsYXRpb25zaGlwIGJldHdlZW4gYWdlIGFuZCBoZWlnaHQuIEl0IGlzIGEgZ29vZCBuZXdzIGZvciBTYXJhaCBhcyBzaGUgbGlrZXMgdGFsbCBndXlzLgoqICoqQWdlIGFuZCBlZHVjYXRpb24qKiAocm93IDQsIGNvbHVtbiAxKTogbm8gc3VycHJpc2UgaGVyZS4gQXMgcGVvcGxlIGVhcm4gaGlnaGVyIGVkdWNhdGlvbiB0aGV5IGdldCBvbGRlci4gNyBpcyBQaEQsIDYgbWFzdGVyLCA1IGJhY2hlbG9yIGV0Yy4uCgojIyMjIyBJbmNvbWUKKiAqKkluY29tZSBncmFwaCoqIChyb3cgMiwgY29sdW1uIDIpOiB0aGVyZSBhcmUgZm91ciBzbWFsbCBodW1wcy4gVGhlIGZpcnN0IGh1bXAgaXMgYXQgJDIwLDAwMC4gTWFpbmx5IHN0dWRlbnRzIGFyZSBpbiB0aGlzIGdyb3VwLiBTZWNvbmQgaHVtcCBpcyBhdCAkODAsMDAwLiBUaGlzIGRhdGEgc2V0IGlzIGZvciBTYW4gRnJhbmNpc2NvIGFyZWEsIHdoZXJlIG1hbnkgZW5naW5lZXJzIGFyZS4gVGhlIGF2ZXJhZ2UgaW5jb21lIGZvciB0aGVtIGlzICQ4NCwwMDAsIHdoaWNoIGlzIGlubGluZSB3aXRoIHRoaXMgZ3JvdXAuIFdlIGFzbG8gc2VlIGEgaHVtcCBhcm91bmQgJDEwMCwwMDAuIFdlIGNvbnNpZGVyIHRoaXMgaXMgZHVlIHRvIGZhY3QgdGhhdCBwZW9wbGUgbGlrZSB0byBzYXkgJDEwMCwwMDAgaWYgdGhlaXIgaW5jb21lIGlzIHJlbGF0aXZlbHkgY2xvc2UgdG8gaXQuIFRoZSBsYXN0IG9uZSBpcyBhdCAkMU0sIHdoaWNoIGlzIHRoZSB1cHBlciBsaW1pdCBmb3IgT0tDdXBpZC4gU29tZSBwZW9wbGUgd2hvIGFjdHVhbGx5IG1ha2UgbW9yZSBtb25leSB3b3VsZCBjaG9vc2UgaXQuIEFub3RoZXIgcmVhc29uIHdlIGNhbiB0aGluayBvZiBpcyB0aGF0IHNvbWUgcGVvcGxlIHdobyB0aXJlZCB0byBiZSBmdW5ueSBvciBkZWNpZGVkIG5vdCB0byBhbnN3ZXIgdGhpcyBxdWVzdGlvbiBhbmQgY2hvc2UgaXQuCiogKipJbmNvbWUgYW5kIGhlaWdodCoqIChyb3cgMywgY29sdW1uIDIpOiB0aGVyZSBpcyBubyByZWxhdGlvbnNoaXAgYmV0d2VlbiBpbmNvbWUgYW5kIGhlaWdodAoqICoqSW5jb21lIGFuZCBlZHVjYXRpb24qKiAocm93IDQsIGNvbHVtbiAyKTogdGhlcmUgaXMgYSBzbGlnaHQgcmVsYXRpb25zaGlwLiBBcyBwZW9wbGUgZWFybiBoaWdoZXIgZWR1Y2F0aW9uIHRoZWlyIGluY29tZSB0ZW5kcyB0byBnbyB1cCBhIGJpdAoKIyMjIyMgSGVpZ2h0CiogKipIZWlnaHQgZ3JhcGgqKiAocm93IDMsIGNvbHVtbiAzKTogaGVpZ2h0IGlzIG5vcm1hbGx5IGRpc3RyaWJ1dGVkCgojIyMjIyBFZHVjYXRpb24KKiAqKkVkdWNhdGlvbiBncmFwaCoqIChyb3cgMywgY29sdW1uIDMpOiB0aGVyZSBhcmUgdHdvIGh1bXBzLiBUaGUgZmlyc3Qgb25lIGlzIGF0IDIsIHdoaWNoIGlzIGEgc3R1ZGVudCBncm91cC4gRGF0YSBzaG93cyB0aGF0IGEgbG90IG9mIHN0dWRlbnRzIHVzZSBPS0N1cGlkLiBEZXNwaXRlIHRoZSBmYWN0IHRoYXQgdGhlcmUgYXJlIG1hbnlvcHBvcnR1bml0aWVzIHRvIHNvY2lhbGl6ZSBhbmQgbWVldCBuZXcgcGVvcGxlLiBUaGUgb3RoZXIgb25lIGlzIGF0IDUsIHdoaWNoIGlzIGJhY2hlbG9yIGRlZ3JlZS4gCgoKIyMjIDUuNiBSZWxhdGlvbnNoaXAgb2YgaW5jb21lIGFuZCBvdGhlciBrZXkgdmFyaWFibGVzIHdpdGggaGVhdG1hcAoKYGBge3J9CiBjb21wbGV0ZV9wcm9maWxlJGFnZV9iaW4gPC0gY3V0KGNvbXBsZXRlX3Byb2ZpbGUkYWdlLCBicmVha3M9c2VxKDEwLCA3MCwgYnk9NSkpCgppbmNvbWVfY291bnRfYWdlX2pvYiA8LSBjb21wbGV0ZV9wcm9maWxlICU+JSBncm91cF9ieShhZ2VfYmluLCBqb2JfZ3JvdXApICU+JSBzdW1tYXJpc2UoSW5jb21lQ291bnQgPSBuKCksIEF2ZXJhZ2VJbmNvbWUgPSBtZWFuKGluY29tZSkpCgp0aXRsZSA8LSAnQXZlcmFnZSBJbmNvbWUgYnkgSm9iIGFuZCBBZ2UnCnN1YmhlYWRlciA8LSAnV2hpdGUgbnVtYmVycyBpbmRpY2F0ZSB0aGUgY291bnQnCgpnZ3Bsb3QoaW5jb21lX2NvdW50X2FnZV9qb2IsIGFlcyhhZ2VfYmluLCBqb2JfZ3JvdXAsIGZpbGwgPSBBdmVyYWdlSW5jb21lKSkgKwogIGdlb21fdGlsZSgpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gSW5jb21lQ291bnQpLCBjb2xvdXIgPSAid2hpdGUiLCBzaXplID0gMykgKwogIGdndGl0bGUoYnF1b3RlKGF0b3AoYm9sZCguKHRpdGxlKSksIGF0b3AoLihzdWJoZWFkZXIpLCAiIikpKSkgKwogIHhsYWIoJ0FnZScpICsgCiAgeWxhYignSm9iIGdyb3VwJykgKyAKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkgKwogIGd1aWRlcyhjb2xvciA9IEZBTFNFKSArIAogIHNjYWxlX2ZpbGxfdmlyaWRpcygpCgpgYGAKVGhpcyBoZWF0bWFwIHRlbGxzIHNvbWUgaW50ZXJlc3Rpbmcgc3Rvcmllcy4gCgoqIFBlb3BsZSBpbiB0cmF2ZWwgaW5kdXN0cmllcyBkbyBub3QgbWFrZSBtdWNoIG1vbmV5IHJlZ2FyZGxlc3Mgb2YgdGhlaXIgYWdlCiogRnJvbSB0aGlzIGRhdGEgd2UgY2FuIHNlZSB0aGF0IHBlb3BsZSBpbiB0ZWNoIGFyZSByZWxhdGl2ZWx5IGRvaW5nIHdlbGwuIEdpdmVuIHRoZSBmYWN0IHRoYXQgdGhpcyBmb3IgU2FuIEZyYW5jaXNjbywgaXQgbWFrZXMgc2Vuc2UuIFNhcmFoIHdvdWxkIGJlIGhhcHB5IHRvIGtub3cgdGhhdCB0aGVyZSBhcmUgMzMxIHBlb3BsZSB3aG8gZml0IGluIGhlciBjcml0ZXJpYS4KKiBQZW9wbGUgaW4gbWVkaWNhbCBmaWVsZCBtYWtlIGdvb2QgbW9uZXkgbGF0ZXIgaW4gdGhlaXIgY2FyZWVyLgoqIEV4ZWN1dGl2ZSBpcyB0aGUgZXhjZXB0aW9uIGhlcmUuIEl0IGRlZmluZXMgdGhlIHNlbmlvcml0eSBpbiBjb21wYW5pZXMuIEFsbCB0aGUgb3RoZXIgb25lcyBhcmUgaW5kdXN0cmllcy4gU28gdGhlcmUgaXMgbm8gc3VycHJpc2UgdGhhdCBleGVjdXRpdmVzIG1ha2UgbW9yZSBtb25leSB0aGFuIG90aGVyIGdyb3Vwcy4KKiBBcnRpc3QgaXMgYW4gaW50ZXJlc3RpbmcgY2F0ZWdvcnkuIFRoZXJlIGFyZSBhIGxvdCBvZiBhcnRpc3RzIHdobyBhcmUgaW4gdGhlaXIgMjBzIGFuZCAzMHMgYW5kIHRoZXkgZG8gbm90IG1ha2UgbXVjaCBtb25leS4gSG93ZXZlciwgb25jZSB0aGV5IGdldCBpbnRvIHRoZWlyIDQwcywgdGhlIHBvcHVsYXRpb24gc2lnbmlmaWNhbnRseSBkZWNyZWFzZXMgYW5kIHRoZSBhdmVyYWdlIGluY29tZSBnb2VzIHVwIGNsb3NlIHRvICQxMDAsMDAwLiAKCgpgYGB7cn0KaW5jb21lX2NvdW50X3NleF9qb2IgPC0gY29tcGxldGVfcHJvZmlsZSAlPiUgZ3JvdXBfYnkoc2V4LCBqb2JfZ3JvdXApICU+JSBzdW1tYXJpc2UoSW5jb21lQ291bnQgPSBuKCksIEF2ZXJhZ2VJbmNvbWUgPSBtZWFuKGluY29tZSkpCgpvcHRpb25zKHNjaXBlbj0xMDAwMCkKCmdncGxvdChpbmNvbWVfY291bnRfc2V4X2pvYiwgYWVzKHNleCwgam9iX2dyb3VwLCBmaWxsID0gQXZlcmFnZUluY29tZSkpICsKICBnZW9tX3RpbGUoKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHNwcmludGYoJyQlcycsIGZvcm1hdChyb3VuZChBdmVyYWdlSW5jb21lLCAwKSwgYmlnLm1hcms9JywnKSkpLCBjb2xvdXIgPSAncmVkJywgc2l6ZSA9IDMpICsKICBnZ3RpdGxlKCdBdmVyYWdlIEluY29tZSBieSBKb2IgYW5kIEdlbmRlcicpICsKICB4bGFiKCdHZW5kZXInKSArIAogIHlsYWIoJ0pvYiBncm91cCcpICsgCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpICsKICBndWlkZXMoY29sb3IgPSBGQUxTRSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcygpICsKICBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscz1jKCdGZW1hbGUnLCAnTWFsZScpKQpgYGAKCldoZW4gd2UgbG9vayBhdCB0aGlzIGhlYXRtYXAsIHdlIGNhbiB0ZWxsIHNvbWUgbW9yZSBpbnRlcmVzdGluZyBzdG9yaWVzLiAKCiogVHJhdmVsIGluZHVzdHJ5IGlzIHRoZSBvbmx5IGZpZWxkIHdoZXJlIHRoZXJlIGlzIG5vIGluY29tZSBnYXAgYmV0d2VlbiBnZW5kZXJzLgoqIFRoZXJlIGFyZSBhIGxvdCBsZXNzIGZlbWFsZXMgaW4gdGVjaCBpbmR1c3RyeSBhbmQgYWxzbyB0aGV5IG1ha2UgbGVzcyBtb25leSB0aGFuIG1hbGVzLgoqIFN1cnByaXNpbmdseSBwZW9wbGUgaW4gbWVkaWNhbCBmaWVsZCBkbyBub3QgbWFrZSBtdWNoIG1vbmV5LiBUaGlzIGlzIGxpa2VseSBkdWUgdG8gdGhhdCBmYWN0IHRoYXQgdGhlaXIgc2FsYXJ5IGlzIGxvdyBkdXJpbmcgdGhlaXIgcmVzaWRlbmN5IGFuZCBmZWxsb3dzaGlwLgoqIEluY29tZSBnYXAgYW1vbmcgbWFsZXMgYW5kIGZlbWFsZXMgaXMgdGhlIGxhcmdlc3QgZm9yIGZpbmFuY2lhbCBpbmR1c3RyeS4gQWxzbyBtYWxlIGZpbmFuY2lhbCBwcm9mZXNzaW9uYWxzIG1ha2UgYWJvdXQgdGhlIHNhbWUgYXMgZXhlY3V0aXZlcy4KCiMjIyA1LjcgRmluZCBhbiBpZGVhbCBjYW5kaWRhdGUhCgpgYGB7cn0KI29ubHkgc2VsZWN0IHNpbmdsZSBwZW9wbGUKCmVkdWNhdGlvbl9vcmRlciA9IGMoJ290aGVyJywgJ2luIHNjaG9vbCcsICdoaWdoIHNjaG9vbCcsICdhc3NvY2lhdGUnLCAnYmFjaGVsb3InLCAnbWFzdGVyJywgJ3BoZCcpCmNvbXBsZXRlX3Byb2ZpbGUkZWR1Y2F0aW9uX2dyb3VwID0gZmFjdG9yKGNvbXBsZXRlX3Byb2ZpbGUkZWR1Y2F0aW9uX2dyb3VwLCBsZXZlbHM9ZWR1Y2F0aW9uX29yZGVyKQoKc2luZ2xlIDwtIGNvbXBsZXRlX3Byb2ZpbGVbY29tcGxldGVfcHJvZmlsZSRzdGF0dXM9PSdzaW5nbGUnLF0KCmJvZHlfdHlwZV9vcmRlciA9IGMoJ3JhdGhlciBub3Qgc2F5JywgJ3VzZWQgdXAnLCAnc2tpbm55JywgJ3RoaW4nLCAnYXZlcmFnZScsICdhdGhsZXRpYycsICdmaXQnLCAnYSBsaXR0bGUgZXh0cmEnLCAnY3VydnknLCAKICAgICAgICAgICAgICAgICAgICAnZnVsbCBmaWd1cmVkJywgJ2phY2tlZCcsICdvdmVyd2VpZ2h0JykKY29tcGxldGVfcHJvZmlsZSRib2R5X3R5cGUgPSBmYWN0b3IoY29tcGxldGVfcHJvZmlsZSRib2R5X3R5cGUsIGxldmVscz1ib2R5X3R5cGVfb3JkZXIpCgoKc2luZ2xlJGFnZV9iaW4gPC0gY3V0KHNpbmdsZSRhZ2UsIGJyZWFrcz1zZXEoMTAsIDcwLCBieT0xMCkpCnNpbmdsZSRhZ2VfYmluIDwtIHN0cmlfc3ViKHNpbmdsZSRhZ2VfYmluLCAyLDMpCnNpbmdsZSRpbmNvbWVfayA8LSBzaW5nbGUkaW5jb21lIC8gMTAwMApzaW5nbGUkaW5jb21lX2tbc2luZ2xlJGluY29tZV9rID49IDIwMF0gPC0gMjAwCgpncm91cGVkIDwtIHJlbmFtZShjb3VudChzaW5nbGUsIHNleCwgYWdlX2JpbiwgZWR1Y2F0aW9uX2dyb3VwLCBqb2JfZ3JvdXAsIGluY29tZV9rKSwgRnJlcSA9IG4pCgphbGx1dmlhbChncm91cGVkWywxOjVdLCBmcmVxPWdyb3VwZWQkRnJlcSwgYm9yZGVyPU5BLAogICAgICAgICBoaWRlID0gZ3JvdXBlZCRGcmVxIDwgcXVhbnRpbGUoZ3JvdXBlZCRGcmVxLCAuNzUpLAogICAgICAgICBjb2w9aWZlbHNlKGdyb3VwZWQkaW5jb21lX2sgPj0gMTUwLCAncmVkJywgJ2dyYXknKSkKYGBgCgpUaGlzIGFsbHV2aWFsIHBsb3Qgc2hvd3MgaG93IHRoZSBrZXkgZmFjdG9ycyByZWxhdGUgdG8gZWFjaCBvdGhlci4gCgoqIEZpcnN0IHRoaW5nIHdlIG5vdGljZSBpcyB0aGF0IG1vc3Qgb2YgdGhlIHBlb3BsZSB3aG8gbWFrZSBtb3JlIHRoYW4gJDE1MCwwMDAgYXJlIG1hbGUuIEV2ZW4gdGFraW5nIHRoZSBmYWN0IHRoYXQgdGhlcmUgYXJlIG1vcmUgbWFsZSBPS0N1cGlkIHVzZXJzIGludG8gY29uc2lkZXJhdGlvbiwgdGhpcyB3YXMgYSBzdXJwcmlzZSBmb3IgdXMgYW5kIEpvaG4gd291bGQgbm90IGJlIHRvbyBoYXBweSB0byBoZWFyIHRoYXQuIAoqIFNlY29uZCwgd2Ugc2VlIHNvbWUgdGVuZGVuY3kgdGhhdCBwZW9wbGUgd2hvIGhhdmUgZWFybmVkIGhpZ2hlciBlZHVjYXRpb24gbWFrZSBtb3JlIG1vbmV5LiAKKiBUaGlyZCwgYSBsb3Qgb2YgdGhlIHdlYWx0aHkgdXNlcnMgYXJlIG1hbGUgd2hvIGFyZSB0aGVpciAzMHMgYW5kIDQwcy4gVGhpcyBpcyBhIGdyZWF0IG5ld3MgZm9yIFNhcmFoCgpgYGB7cn0KZGF0YV9nZ3BhciA8LSBjb21wbGV0ZV9wcm9maWxlWyxjKCdzZXgnLCAnb3JpZW50YXRpb24nLCAnc3RhdHVzJywgJ2FnZScsICdoZWlnaHQnLCdib2R5X3R5cGUnLCAnZHJpbmtzJywgJ2pvYl9ncm91cCcsICdpbmNvbWUnKV0KCnBhcmNvb3JkcyhkYXRhX2dncGFyCiAgICAgICAgICxyb3duYW1lcyA9IEYgCiAgICAgICAgICxicnVzaE1vZGUgPSAnMUQtYXhlcycKICAgICAgICAgLHJlb3JkZXJhYmxlID0gVAogICAgICAgICAscXVldWUgPSBUCiAgICAgICAgICxjb2xvciA9IGxpc3QoY29sb3JCeSA9ICdSZWdpb24nICxjb2xvclNjYWxlID0gaHRtbHdpZGdldHM6OkpTKCdkMy5zY2FsZS5jYXRlZ29yeTEwKCknKSkpCmBgYApUaGlzIHBhcmFsbGVsIGNvb3JkaW5hdGUgcGxvdCBpcyB2ZXJ5IHBvd2VyZnVsLiBJdCBsZXRzIG91ciBmcmllbmRzIGZpbmQgZXhhY3RseSB0aGUgcGVvcGxlIHRoZXkgd291bGQgbGlrZSB0byBtZWV0LiBIZXJlIGFyZSBob3cgdGhlaXIgaWRlYWwgY2FuZGlkYXRlcyB3b3VsZCBsb29rIGxpa2UuIFRoZXkgaGF2ZSBkaWZmZXJlbnQgcHJlZmVyZW5jZXMgYnV0IGl0IGxvb2tzIGxpa2UgYm90aCBvZiB0aGVtIGhhdmUgbWFueSBvcHRpb25zLgoKCjxkaXYgc3R5bGU9IndpZHRoOjYwMHB4OyBoZWlnaHQ9NTAwcHgiPgo8Yj4tIElkZWFsIGNhbmRpZGF0ZXMgZm9yIEpvaG48L2I+CiFbSW1hZ2VdKGlkZWFsX2pvaG4ucG5nKQo8L2Rpdj4KCjxkaXYgc3R5bGU9IndpZHRoOjYwMHB4OyBoZWlnaHQ9NTAwcHgiPgo8Yj4tIElkZWFsIGNhbmRpZGF0ZXMgZm9yIFNhcmFoPC9iPgohW0ltYWdlXShpZGVhbF9zYXJhaC5wbmcpCjwvZGl2PgoKIyA2LiBDb25jbHVzaW9uCiMjIyBPdmVyYWxsCldlIGZvdW5kIE9LQ3VwaWQgZGF0YXNldCB2ZXJ5IGludGVyZXN0aW5nLiBUaGVyZSBhcmUgYSBsb3Qgb2Ygc3RvcmllcyB3ZSBjb3VsZCB0ZWxsIGJ5IGFuYWx5emluZyB0aGlzIHN1Y2ggYXMgdGhlIHJlbGF0aW9uc2hpcCBvZiBpbmNvbWUgYW5kIG9jY3VwYXRpb24gb3IgYWdlLiBXZSB1c2VkIG1hbnkgZGlmZmVyZW50IHBsb3RzLCB3aGljaCB3ZSBsZWFybmVkIGluIGNsYXNzIHRvIHNsaWNlIGFuZCBkaWNlIHRoZSBkYXRhIHRvIGV4dHJhY3QgbWVhbmluZ2Z1bCBpbmZvcm1hdGlvbi4gQXQgdGhlIGVuZCB3ZSBlZmZlY3RpdmVseSBpZGVudGlmaWVkIG91ciByZWNvbW1lbmRhdGlvbnMgb2YgdGhlIHBlb3BsZSB3aG8gd2UgYmVsaWV2ZSBvdXIgZnJpZW5kcyBKb2huIGFuZCBTYXJhaCBzaG91bGQgbWVldC4gV2Ugd2lsbCB2YWxpZGF0ZSBvdXIgcmVjb21tZW5kYXRpb25zIGFuZCBhbmFseXNpcyBieSBjYXJlZnVsbHkgbW9uaXRvcmluZyB0aGUgcHJvZ3Jlc3Mgb2YgdGhlaXIgZGF0ZXMuIAoKIyMjIExpbWl0YXRpb24KT25lIGtpbmQgb2YgZGF0YSB3ZSB3aXNoIHdlIGhhZCB3YXMgdXNlciBwb3B1bGFyaXR5IG9yIGxpa2VzLiBXaXRoIHRoYXQgd2UgY291bGQgaGF2ZSBhbmFseXplZCB0byB1bmRlcnN0YW5kIHdoYXQgZmVhdHVyZXMgbWFrZSBwZW9wbGUgYXR0cmFjdGl2ZSBlc3BlY2lhbGx5IG9uIG9ubGluZSBkYXRpbmcgc2VydmljZXMuIFRoYXQgd291bGQgaGF2ZSBiZWVuIGEgdmVyeSBpbnRlcmVzdGluZyBhbmFseXNpcy4gV2UgY291bGQgYWxzbyBoYXZlIGJ1aWx0IGEgbWFjaGluZSBsZWFybmluZyBtb2RlbCB0byBwcmVkaWN0IGZ1dHVyZSBsaWtlcyBhbmQgb3VyIGZyaWVuZHMgY291bGQgaGF2ZSBlZGl0ZWQgdGhlaXIgcHJvZmlsZSBiZWZvcmUgc3VibWl0dGluZy4KCkFub3RoZXIgZGF0YSB3ZSB3aXNoIHdlIGhhZCB3YXMgdXNlciBwcm9maWxlIHBpY3R1cmVzLiBUaGV5IHdvdWxkIGhhdmUgZW5hYmxlZCB1cyB0byBjb25kdWN0IHZlcnkgaW50ZXJlc3RpbmcgYW5hbHlzaXMuIEFsb25nIHdpdGggbGlrZXMsIHBpY3R1cmVzLCBhbmQgcHJvZmlsZXMgd2UgYmVsaWV2ZSB3ZSBjYW4gYWNjdXJhdGVseSBwcmVkaWN0IGhvdyBhIG5ldyB1c2VyIHdpbGwgZG8gb24gT0tDdXBpZC4gCgojIyMgRnV0dXJlIFN0ZXBzClRoZSBuZXh0IHN0ZXAgb2YgdGhlIGFuYWx5c2lzIGlzIHRvIGRpZyBtb3JlIGludG8gb3RoZXIgZmVhdHVyZXMgb2YgdGhlIGRhdGFzZXQuIElmIHdlIHdhbnQgdG8gYmUgbW9yZSBhY2N1cmF0ZSBhbmQgZmluZCBvdXIgZnJpZW5kcyBhIHBlcmZlY3QgbWF0Y2gsIHRoZSBiZXN0IGlkZWEgaXMgdG8gbG9vayBpbnRvIHRoZSBzZWxmIGludHJvZHVjdGlvbiBwYXJ0IChmcmVlIHRleHQpIHRvIG1hdGNoIHRoZSBjcml0ZXJpYSB0aGV5IGFyZSBsb29raW5nIGZvci4KCkhlcmUgaXMgYSBxdWljayBleGFtcGxlIG9mIHdoYXQgd2UgY291bGQgZG8gbmV4dC4gSXQgc2hvd3MgdGhlIHBlcmNlbnRhZ2Ugb2YgdGhlIHVzZXJzIHdobyB1c2VkIHRoZSB3b3JkICJsb3ZlIiBpbiB0aGVpciBpbnRyb2R1Y3Rpb24uIFdlIGNhbiBzZWUgdGhhdCBmZW1hbGUgdGVuZCB0byB1c2UgaXQgbW9yZSB0aGFuIG1hbGUuCmBgYHtyfQpva2N1cGlkIDwtIG9rY3VwaWQgJT4lIGZpbHRlcihzZXghPScnICYgc3RhdHVzIT0nJykKZXNzYXlzPC0gc2VsZWN0KG9rY3VwaWQsc3RhcnRzX3dpdGgoJ2Vzc2F5JykpCmVzc2F5czwtIGFwcGx5KGVzc2F5cyxNQVJHSU49MSwgRlVOPXBhc3RlLGNvbGxhcHNlPScgJykKZXNzYXlzPC0gc3RyX3JlcGxhY2VfYWxsKGVzc2F5cywnXG4nLCAnICcpCmVzc2F5czwtIHN0cl9yZXBsYWNlX2FsbChlc3NheXMsJzxiciAvPicsICcgJykKCm9rY3VwaWQkaGFzX2xvdmU8LSBzdHJfZGV0ZWN0KGVzc2F5cywnbG92ZScpCm1vc2FpYzo6dGFsbHkoaGFzX2xvdmUgfiBzZXgsIG9rY3VwaWQsIGZvcm1hdD0ncHJvcG9ydGlvbicpCmBgYAo=